package org.proxywars.engine.scene.shape;

import java.awt.Insets;

import com.jme.image.Texture;
import com.jme.math.Vector2f;
import com.jme.scene.Node;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;

/**
 * This class is to create a rectangle that had a high degree of control over texturing.
 * It will slice the texture into 9 regions and apply the texture to them.
 * This way you can have the corners static, while the rest stretches. 
 * 
 * @author Steven DeVries
 */
public class SlicedRectangle3d extends Node {
    /*
     * NOTE:
     * I simply used 9 quads and move/resize them appropriately.
     * It might be better/faster to actually create a trimesh
     * with 16 points and map the texture properly.
     * 
     * However, I am quite new to jME and it took me long enough
     * just to figure out how to texture a Quad, let alone texture a 16 pt. trimesh.
     * I also don't know how much, if at all, faster it would be.  Is it worth it?
     */

    private static final long serialVersionUID = 1L;
    

    /* Protected Variables */
    protected float width;      // float since Rectangle3d uses them.
    protected float height;
    protected Vector2f origin;  // same here

    /** TextureState used to apply the texture. */
    protected TextureState textureState;
    
    
    /* Private Variables */
    /** Texture applied to the rectangle */
    private Texture texture;
    /** insets of the applied texture */
    private Insets insets = new Insets(0,0,0,0);
    /** transparency of the applied texture */
    private float transparency;
    
    private TexturedRectangle3d   topLeft, topCenter, topRight, 
        centerLeft, centerCenter, centerRight,
        bottomLeft, bottomCenter, bottomRight;


    /* Constructors */
    /**
     * Convenience constructor.
     * Should only really be used once skins are functional.
     */
    public SlicedRectangle3d() {
        
    }
    
    /**
     * Creates a white, 200x100 rectangle at (0,0).
     * 
     * @param name     Name of the rectangle.
     */
    public SlicedRectangle3d(String name) {
        initialize(name, 0, 0, 200, 100);
    }
    
    /**
     * Creates a white rectangle of specified size at (0,0).
     * 
     * @param name     Name of the rectangle.
     * @param width     width of the rectangle.
     * @param height    height of the rectangle.
     */
    public SlicedRectangle3d(String name, int width, int height) {
        initialize(name, 0, 0, width, height);
    }
    
    /**
     * Creates a white rectangle with the specified location and size.
     * 
     * @param name     Name of the rectangle.
     * @param x           x location of top left corner of the rectangle.
     * @param y            y location of the top left corner of the rectangle.
     * @param width     width of the rectangle.
     * @param height    height of the rectangle.
     */
    public SlicedRectangle3d(String name, int x, int y, int width, int height) {
        initialize(name, x, y, width, height);
    }
    
    /**
     * Convenience function so that all the constructors just call one function.
     * Makes it easier to change/update or for children to initialize all the values as well.
     */
    protected void initialize(String name, int x, int y, int width, int height) {
        origin = new Vector2f(x, y);        
        this.width = width;
        this.height = height;
        this.name = name;
        
        /* intitialize all the rectangles */
        topLeft            = new TexturedRectangle3d("topLeft");
        topCenter        = new TexturedRectangle3d("topCenter");
        topRight           = new TexturedRectangle3d("topRight");
        centerLeft        = new TexturedRectangle3d("centerLeft");
        centerCenter    = new TexturedRectangle3d("centerCenter");
        centerRight       = new TexturedRectangle3d("centerRight");
        bottomLeft       = new TexturedRectangle3d("bottomLeft");
        bottomCenter   = new TexturedRectangle3d("bottomCenter");
        bottomRight     = new TexturedRectangle3d("bottomRight");
        
        /* Debug colors */
        /*
        topLeft.setDefaultColor(ColorRGBA.red); 
        topCenter.setDefaultColor(ColorRGBA.white); 
        topRight.setDefaultColor(ColorRGBA.orange);  
        centerLeft.setDefaultColor(ColorRGBA.pink);      
        centerCenter.setDefaultColor(ColorRGBA.gray);   
        centerRight.setDefaultColor(ColorRGBA.yellow); 
        bottomLeft.setDefaultColor(ColorRGBA.green); 
        bottomCenter.setDefaultColor(ColorRGBA.brown);
        bottomRight.setDefaultColor(ColorRGBA.blue);
        */
        
        attachChild(topLeft);
        attachChild(topCenter);
        attachChild(topRight);
        attachChild(centerLeft);
        attachChild(centerCenter);
        attachChild(centerRight);
        attachChild(bottomLeft);
        attachChild(bottomCenter);
        attachChild(bottomRight);
        
        /* Make sure all sections are stretched: 
         * tiling of north, south, east, and west regions is not yet implemented.
         * 
         * NOTE: According to the forums, it's impossible.  Except maybe with shaders... maybe.
         *           So for now this will remain un-implemented.
         *           
         *  http://www.jmonkeyengine.com/jmeforum/index.php?topic=9899.0
         */
        for(int i=0; i<children.size(); i++) {
            TexturedRectangle3d tmp = (TexturedRectangle3d)children.get(i);
            tmp.setTextureMode(TexturedRectangle3d.TEXTURE_STRETCHED);
        }
        
        setTransparency(1f);
        setInsets(0, 0, 0, 0); // sets location, size, and updates render state.
    }
    
    
    /* Get Methods */
    /**
     * Get the height of the rectangle.
     * @return  the height of the rectangle.
     */
    public float getHeight() {
        return height;
    }
    
    /**
     * Get the insets used to slice the texture.
     * @return  the insets used for slicing the texture.
     */
    public Insets getInsets() {
        return insets;
    }
    
    /**
     * Get the texture applied to this rectangle.
     * @return  the applied texture.
     */
    public Texture getTexture() {
        return texture;
    }
    
    /**
     * Get the transparency of the rectangle.
     * 
     * @return the transparency of the rectangle.
     */
    public float getTransparency() {
        return transparency;
    }
    
    /**
     * Get the width of the rectangle.
     * @return  the height of the rectangle.
     */
    public float getWidth() {
        return width;
    }
    
    /**
     * Returns the x position of the top left corner of the Rectangle 
     * in terms of a traditional (x,y) coordinates as opposed to OpenGL's
     * (u,v) coordinates.
     * 
     * @return the x position of the top left corner.
     */
    public int getX() {
        return (int)origin.x;
    }
    
    /**
     * Returns the y position of the top left corner of the Rectangle 
     * in terms of a traditional (x,y) coordinates as opposed to OpenGL's
     * (u,v) coordinates.
     * 
     * @return the y position of the top left corner.
     */    
    public int getY() {
        return (int)origin.y;
    }
    
    
    /* Set Methods */
    /**
     * Set the insets used to slice the texture.
     * @param top           height of the top area.
     * @param bottom     height of the bottom area.
     * @param left           width of the left area.
     * @param right         width of the right area.
     */
    public void setInsets(int top, int bottom, int left, int right) {
        insets.top = top;
        insets.bottom = bottom;
        insets.left = left;
        insets.right = right;
        setSize( (int) width, (int) height);     // resize the object with the new insets
    }
    
    /**
     * Set the insets used to slice the texture.
     * @param insets    insets used for slicing.
     */
    public void setInsets(Insets insets) {
        this.insets = insets;
        setSize( (int) width, (int) height);     // resize the object with the new insets
    }
    
    
    /**
     * Sets the location of the rectangle on the screen.
     * This function uses traditional (x, y) coordinates, (0,0) being top left corner.
     * The top, left corner of the rectangle will be at the specified point.
     * 
     * @param x     horizontal starting point.
     * @param y     vertical starting point.
     */
    public void setLocation(int x, int y) {
        origin.x = (float)x;
        origin.y = (float)y;
        
        int centerHeight = (((int) height) - insets.top) - insets.bottom;
        int centerWidth = (((int) width) - insets.right) - insets.left;
        
        topLeft.setLocation(     x, y);
        centerLeft.setLocation( x, y + insets.top);
        bottomLeft.setLocation(x, y + insets.top + centerHeight);
 
        topCenter.setLocation(     x+ insets.left, y);
        centerCenter.setLocation( x+ insets.left, y + insets.top);
        bottomCenter.setLocation(x+ insets.left, y + insets.top + centerHeight);
        
        topRight.setLocation(     x+ insets.left + centerWidth, y);
        centerRight.setLocation( x+ insets.left + centerWidth, y + insets.top);
        bottomRight.setLocation(x+ insets.left + centerWidth, y + insets.top + centerHeight);
        
        updateRenderState();
    }
    
    
    /**
     * Set the size of the rectangle.
     * @param width     width of the rectangle.
     * @param height    height of the rectangle.
     */
    public void setSize(int width, int height) {
        this.width = (float) width;
        this.height = (float) height;
        
        int centerWidth = (width - insets.right) - insets.left;
        int centerHeight = (height - insets.top) - insets.bottom;        
        
        /* set the 4 corners to the size of insets */
        topLeft.setSize(      insets.left, insets.top);
        bottomLeft.setSize(insets.left, insets.bottom);
        
        topRight.setSize(     insets.right, insets.top);
        bottomRight.setSize(insets.right, insets.bottom);
        
        /* set the rest of the regions to the differences in size */
        topCenter.setSize(centerWidth,  insets.top);
        bottomCenter.setSize(centerWidth,  insets.bottom);
        
        centerLeft.setSize(insets.left, centerHeight);
        centerRight.setSize(insets.right, centerHeight);
        centerCenter.setSize(centerWidth, centerHeight);
        updateRenderState();
        
        /* relocate the rectangles to line up again. */
        setLocation( (int) origin.x, (int) origin.y);
    }
    
    /**
     * Set the texture for this rectangle to apply.
     * @param fileName  name of the file.
     */
    public void setTexture(String fileName) {
        setTexture(fileName, 0, 0, 0, 0);   
    }
    
    /**
     * Set the texture for this rectangle to apply.
     * @param fileName                      name of image to set as texture.
     * @param textureX                      X point of top left pixel in file.
     * @param textureY                      Y point of top left pixel in file.
     * @param texturePixelWidth         width of the image within the texture.
     * @param texturePixelHeight        height of the image within the texture.
     */
    public void setTexture(String fileName, int textureX, int textureY, int texturePixelWidth, int texturePixelHeight) {
        texture = TextureManager.loadTexture(
                getClass().getResource(fileName),
                Texture.MinificationFilter.Trilinear, // of no use for the quad
                Texture.MagnificationFilter.Bilinear, // of no use for the quad
                1.0f,
                true);
        
        texturePixelWidth = (texturePixelWidth == 0 ? texture.getImage().getWidth() : texturePixelWidth);
        texturePixelHeight = (texturePixelHeight == 0 ? texture.getImage().getHeight() : texturePixelHeight);
        
        setTexture(texture, textureX, textureY, texturePixelWidth, texturePixelHeight);
    }
    
    /**
     * Set the texture for this rectangle to apply.
     * @param texture   texture to apply.
     */
    public void setTexture(Texture texture, int textureX, int textureY, int texturePixelWidth, int texturePixelHeight) {
        this.texture = texture;
        
        int centerWidth = texturePixelWidth - insets.right - insets.left;
        int centerHeight = texturePixelHeight - insets.top - insets.bottom;
        
        // texture, x, y, width, height
        /* Left */
        topLeft.setTexture(       texture, 
                                            textureX, textureY,                     
                                            insets.left, insets.top);
        
        centerLeft.setTexture(   texture, 
                                            textureX, textureY+insets.top, 
                                            insets.left, centerHeight);
        
        bottomLeft.setTexture(  texture, 
                                            textureX, textureY+insets.top+centerHeight,  
                                            insets.left, insets.bottom);
        
        /* Center */
        topCenter.setTexture(      texture, 
                                              textureX+insets.left, textureY,
                                              insets.left, insets.top);
        
        centerCenter.setTexture( texture, 
                                              textureX+insets.left, textureY+insets.top, 
                                              centerWidth, centerHeight);
        
        bottomCenter.setTexture(texture, 
                                              textureX+insets.left, textureY+insets.top+centerHeight, 
                                              insets.left, insets.bottom);
        
        /* Right */
        topRight.setTexture(      texture, 
                                            textureX+insets.left+centerWidth, textureY,
                                            insets.left, insets.top);
        
        centerRight.setTexture( texture, 
                                            textureX+insets.left+centerWidth, textureY+insets.top, 
                                            insets.left, centerHeight);
        
        bottomRight.setTexture(texture, 
                                            textureX+insets.left+centerWidth, textureY+insets.top+centerHeight,
                                            insets.left, insets.bottom);
        
        updateRenderState();
    }
    
    /**
     * Set the transparency of the rectangle.
     * @param transparency  the transparency of the rectangle (0-1f).
     */
    public void setTransparency(float transparency) {
        this.transparency = transparency;
        for(int i=0; i<children.size(); i++) {
            TexturedRectangle3d tmp = (TexturedRectangle3d)children.get(i);
            tmp.setTransparency(transparency);
        }
        
        updateRenderState();
    }
    
}
